import java.util.Random;
import java.util.Arrays;

// Space is the actual program (executable class) using objects of class 'Body'.
public class Space {

    // Some constants helpful for the simulation (particularly the 'falling ball' example).
    public static final double G = 6.6743e-11; // gravitational constant
    public static final double MASS_OF_EARTH = 5.972e24; // kg
    public static final double MASS_OF_BALL = 1; // kg
    public static final double RADIUS_OF_EARTH = 6.371e6; // m (meters)

    // On the surface of earth the gravitational force on the ball weighing 1kg is
    // approximately as follows:
    public static final double GRAVITATIONAL_FORCE_ON_BALL =
            G*MASS_OF_EARTH*MASS_OF_BALL/(RADIUS_OF_EARTH*RADIUS_OF_EARTH); // kg*m/sec² ... F = mass * acc
    // This means each second its speed increases about 9.82 meters per second.

    public static final double ROCKET_VERTICAL_DISPLACEMENT = 5e6d;
    public static final double AIR_FORCE_FACTOR = .5d;

    // have a gravitational force set for the duration of a simulation with
    // regard to the body that is currently being tested
    public static double current_grav_force;

    public static Random random = new Random();

    // The main simulation method using instances of other classes.
    public static void main(String[] args) {
        //Results for the falling ball on the surface of earth are as follows:
        //Height 10m: 2 (sec = number of move(fx,fy,fz) calls)
        //Height 100m: 5 (sec = number of move(fx,fy,fz) calls)

        /* EARTHLY GRAVITY SIMULATION */

        Body ball1 = new Body();
        ball1.setPosition(0,0,100); // 100m height.
        ball1.setVelocity(0,0,0);
        ball1.setMass(1); // 1 kg
        current_grav_force = getGravitationalForceOnEarth(ball1);
        System.out.println(fallToGround(ball1)); // 5

        ball1.setPosition(0,0,10); // 10m height.
        ball1.setVelocity(0,0,0);
        System.out.println(fallToGround(ball1)); // 2

        Body ball2 = new Body();
        ball2.setPosition(0,0,100); // 100m height.
        ball2.setVelocity(0,0,0);
        ball2.setMass(15); // 15 kg
        current_grav_force = getGravitationalForceOnEarth(ball2);
        System.out.println(fallToGround(ball2)); // 5

        /* OBJECTS FLYING THROUGH SPACE @ CONSTANT SPEED */

        Body spaceObject = new Body();
        spaceObject.setMass(1e3);
        spaceObject.setVelocity(2e2d, 3e3d, 4e4d);
        System.out.println(Arrays.toString(simulateSpaceBody(spaceObject, 10)));

        /* ROCKET FLYING THROUGH ATMOSPHERE @ ACCELERATED SPEED */

        Body rocket = new Body();
        rocket.setMass(2e6d + 5e5d);
        /*
        a background story on chosen simulation parameters:
        https://www.quora.com/How-much-does-the-fire-on-a-rocket-weigh-and-is-this-calculated-into-the-gross-weight-of-the-rocket
        On the launch pad, once the turbopumps started pumping RP1 kerosine &
        Liquid oxygen into the 5 F1 engines and ignite the mixture…you have
        flame. This stage pumped an average of about 10,000 lbs (4600 Kg) of
        these propellants per second…and pretty much all of it became flame.
        Mostly this was made up of steam and carbon dioxide and, when flame
        cooled it turn to smoke/steam (CO2 & H20 is soda water making the
        S-IC basically a giant Seltzer bottle!) At burn out, over
        4.5 million lbs (2 million Kgs) of fire had been created.
         */
        System.out.println(simulateRocketBody(rocket, 165, 2e6d/165d));

        /* EARTHLY GRAVITY SIMULATION WITH INITIALIZATION */

        Body ball3 = new Body();
        ball3.setMass(20);
        System.out.println(fallToGround(ball3, 25));

        /* FEATHER DRIFTING FREELY BY RANDOM (aerial) FORCES */

        Body feather = new Body();
        feather.setMass(4.54e-1d);
        simulateFeatherBody(feather, 100);
    }

    /**
     * returns the number of move(fx,fy,fz) calls needed for 'b' hitting the
     * ground, i.e., the method reduces the z-coordinate of 'b' until it
     * becomes 0 or negative.
     * @param b body to apply gravitational force to
     * @return seconds until ground is hit by body b
     */
    public static int fallToGround(Body b) {
        System.out.println(b);

        if (b.atGround()) return 0;

        b.move(0d, 0d, -current_grav_force);

        return 1 + fallToGround(b);
    }

    /**
     * fallToGround wrapper with option for initialization of position
     * @param b body to apply gravitational force to
     * @param z height to initialize position with
     * @return seconds until ground is hit by body b
     */
    public static int fallToGround(Body b, double z) {
        b.setPosition(0, 0, z);
        current_grav_force = getGravitationalForceOnEarth(b);
        return fallToGround(b);
    }

    /**
     * simulates a rocket body for a specified duration with a customizable
     * fuel burn parameter
     * @param b rocket like body to simulate
     * @param seconds duration in seconds
     * @param fuel_burn mass in kg lost per second
     * @return new vertical displacement after simulation
     */
    public static double simulateRocketBody(Body b, int seconds, double fuel_burn) {
        System.out.println(b);

        if (seconds <= 0) return b.height();

        b.move(0d, 0d, ROCKET_VERTICAL_DISPLACEMENT);
        b.setMass(b.getMass() - fuel_burn);

        return 0d + simulateRocketBody(b, --seconds, fuel_burn);
    }

    /**
     * wrapper for simulateRocketBody with constant fuel_burn of 0
     * @param b rocket like body to simulate
     * @param seconds duration in seconds
     * @return new vertical displacement after simulation
     */
    public static double[] simulateSpaceBody(Body b, int seconds) {
        if (seconds <= 0) return b.position();

        b.move();

        return simulateSpaceBody(b, --seconds);
    }

    /**
     * overloaded wrapper for simulateRocketBody(Body,int,double) with
     * zero fuel burn
     * @param b rocket like body to simulate
     * @param seconds duration in seconds
     * @return new vertical displacement after simulation
     */
    public static double simulateRocketBody(Body b, int seconds) {
        return simulateRocketBody(b, seconds, 0d);
    }

    /**
     * simulates feather body
     * @param b feather like body
     * @param seconds duration in seconds
     */
    public static void simulateFeatherBody(Body b, int seconds) {
        System.out.println(b);

        if (seconds <= 0) return;

        int x_sign = random.nextDouble() < .5d ? -1 : 1;
        int y_sign = random.nextDouble() < .5d ? -1 : 1;
        int z_sign = random.nextDouble() < .5d ? -1 : 1;
        b.move(
                x_sign * AIR_FORCE_FACTOR * random.nextDouble(),
                y_sign * AIR_FORCE_FACTOR * random.nextDouble(),
                z_sign * AIR_FORCE_FACTOR * random.nextDouble()
        );

        simulateFeatherBody(b, --seconds);
    }

    /**
     * returns the amount of gravitational force that is applied to an
     * arbitrary body b on the surface of the earth
     * @param b the body on which a force is exerted
     * @return the force in kg*m/sec²
     */
    private static double getGravitationalForceOnEarth(Body b) {
        return G * MASS_OF_EARTH * b.getMass() / (RADIUS_OF_EARTH * RADIUS_OF_EARTH);
    }
}
